#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <aio.h>
#include <signal.h>

int start_server()
{
	struct addrinfo hints, *rp, *result;
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;
	hints.ai_protocol = IPPROTO_TCP;

	int ret = getaddrinfo(NULL, "8000", &hints, &result);
	if (ret) {
		perror("getaddrinfo");
		_exit(EXIT_FAILURE);
	}

	int fd = 0;
	for (rp = result; rp; rp = rp->ai_next) {
		fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (fd < 0) {
			continue;
		}
		int on = 1;
		if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
			perror("setsockopt");
			close(fd);
			continue;
		}
		if (bind(fd, rp->ai_addr, rp->ai_addrlen) < 0) {
			close(fd);
			continue;
		}
		listen(fd, 5);
		break;
	}
	if (rp == NULL) {
		perror("create server fail");
		_exit(EXIT_FAILURE);
	}
	freeaddrinfo(result);
	return fd;
}

void td(union sigval val)
{
	struct aiocb *cb = val.sival_ptr;
	if (aio_error(cb) == 0) {
		int len = aio_return(cb);
		if (len == 0) {
			printf("client %d close conn\n", cb->aio_fildes);
			close(cb->aio_fildes);
			free(cb);
			return;
		}
		char *ptr = (char *)cb->aio_buf;
		ptr[len] = 0;
		printf("recv from=%d %s", cb->aio_fildes, ptr);
	} else {
		perror("error");
		close(cb->aio_fildes);
		free(cb);
		return;
	}
	aio_read(cb);
	return;
}

void sig_act(int sig, siginfo_t *info, void *context)
{
	struct aiocb *cb = info->si_value.sival_ptr;
	int len = aio_return(cb);
	char *ptr = (char *)cb->aio_buf;
	ptr[len] = 0;
	printf("recv from=%d %s", cb->aio_fildes, ptr);
	if (len > 0) {
		aio_read(cb);
		return;
	}
	free(cb);
}

int main(int argc, char **argv)
{
	int opt, method;
	while ((opt = getopt(argc, argv, "stn")) != -1) {
		switch (opt) {
		case 's':
			method = SIGEV_SIGNAL;
			break;
		case 't':
			method = SIGEV_THREAD;
			break;
		case 'n':
			method = SIGEV_NONE;
			break;
		default:
			printf("unknow method");
			_exit(EXIT_FAILURE);
		}
	}
	if (method == SIGEV_SIGNAL) {
		struct sigaction act;
		memset(&act, 0, sizeof(act));
		act.sa_flags = SA_RESTART | SA_SIGINFO;
		act.sa_sigaction = sig_act;
		if (sigaction(SIGIO, &act, NULL) < 0) {
			perror("sigaction");
			_exit(EXIT_FAILURE);
		}
	}

	int fd = start_server();
	struct aiocb *iocb;
	char buf[1024];
	while (1) {
		int cfd = accept(fd, NULL, NULL);
		iocb = calloc(sizeof(struct aiocb), 1);
		iocb->aio_fildes = cfd;
		iocb->aio_buf = buf;
		iocb->aio_nbytes = sizeof(buf);
		iocb->aio_offset = 0;
		iocb->aio_sigevent.sigev_notify = method;
		if (method == SIGEV_THREAD) {
			iocb->aio_sigevent.sigev_notify_function = td;
			iocb->aio_sigevent.sigev_value.sival_ptr = iocb;
		}
		if (method == SIGEV_SIGNAL) {
			iocb->aio_sigevent.sigev_signo = SIGIO;
			iocb->aio_sigevent.sigev_value.sival_ptr = iocb;
		}
		aio_read(iocb);
	}
	return EXIT_SUCCESS;
}